
/* <<< COPYRIGHT START >>>
 * Copyright 2006-Present OxygenSoftwareLibrary.com
 * Licensed under the GNU Lesser General Public License.
 * http://www.gnu.org/licenses/lgpl.html
 * 
 * @author: Ugorji Dick-Nwoke
 * <<< COPYRIGHT END >>>
 */


package oxygen.extensions.tomcat; 

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.connector.Connector;

/**
 * conf/jk/workers.properties             (dynamic - rewritten when tomcat is started)
 * conf/jk/httpd.conf.include             (ditto)
 * conf/jk/httpd.conf.include.$hostname   (ditto)
 * - this is a snippet, which should be directly included into the virtualhost def
 * Assumptions: No default webapp, all exploded webapps
 * 
 * For now, NOT YET SUPPORTED BELOW:
 * # conf/jk/mountfile.properties.hostname  (dynamic - rewritten whenever a webapp is (un/re)deployed)
 * # we will not support mountfile yet, since we can't get 2.13 mod_jk
 */
public class OxygenModJkAutoConfigureListener implements LifecycleListener {

  //must end in a /
  private String catalinaBase = null;
  private String modulePath = null;
  private String logfile = null;
  private String options = null;
  //sometimes, we need to run this many times
  private long interval = 0;
  
  private TimerTask timerTask = null;
  private Timer timer = null;
  
  public OxygenModJkAutoConfigureListener() {    
    String s = System.getProperty("catalina.base");
    if(s == null) {
      s = System.getProperty("catalina.home");
    }
    if(s == null) {
      s = System.getProperty("user.dir").replace('\\', '/');
    }
    setCatalinaBase(s);
    setModulePath(catalinaBase + "conf/jk/mod_jk.so");
    setLogfile(catalinaBase + "logs/mod_jk.log");
    //setInterval(5000l * 60);
    setInterval(0);
    setOptions("+Indexes +FollowSymLinks");
  }
  
  public void setOptions(String s) {
    options = s;
  }
  
  public void setLogfile(String s) {
    logfile = s;
  }
  
  public void setCatalinaBase(String s) {
    catalinaBase = s;
    if(!(catalinaBase.endsWith("/"))) {
      catalinaBase = catalinaBase + "/";
    }
  }
  
  public void setModulePath(String s) {
    modulePath = s;
  }

  public void setInterval(long timeMs) {
    interval = timeMs;
  }
  
  public void lifecycleEvent(LifecycleEvent evt) {
    File f = null;
    f = new File(catalinaBase + "conf/jk");
    f.mkdirs();
    
    Lifecycle who = evt.getLifecycle();
    if (who != null && who instanceof Engine) {
      Engine engn = (Engine)who;
      try {
        if(Lifecycle.START_EVENT.equals(evt.getType())) {
          handleStartEvent(engn);
        } else if(Lifecycle.STOP_EVENT.equals(evt.getType())) {
          handleStopEvent(engn);
        }   
      } catch(RuntimeException rtexc) {
        logError(engn, rtexc);
        throw rtexc;
      } catch(Exception exc) {
        logError(engn, exc);
        throw new RuntimeException(exc);
      }
    }
  }
  
  private void logError(Engine engn, Throwable exc) {
    engn.getLogger().error("Problem while recreating Apache Configuration: ", exc);
    exc.printStackTrace();
  }
  
  private void handleStartEvent(final Engine engn) throws Exception {
    if(interval > 0) {
      timerTask = new TimerTask() {
          public void run() {
            try {
              recreateApacheConf(engn);
            } catch(Exception exc) {
              logError(engn, exc);
              //cancel();
            }
          }
        };
      timer = new Timer(true);
      timer.schedule(timerTask, 200l, interval);
    } else {
      recreateApacheConf(engn);
    } 
  }
  
  private void handleStopEvent(final Engine engn) throws Exception {
    if(timerTask != null) timerTask.cancel();
    if(timer != null) timer.cancel();
    timerTask = null;
    timer = null;
  }
  
  private void recreateHostConfFiles(Host host) throws Exception {
    String workerName = host.getName();
    //String workerName = host.getName().replace('.', '_');

    //PrintWriter mountfile = new PrintWriter(new FileOutputStream(catalinaBase + "conf/jk/mountfile.properties." + workerName));
    PrintWriter httpdconf = new PrintWriter(new FileOutputStream(catalinaBase + "conf/jk/httpd.conf.include." + workerName));
    
    //httpdconf.println("  JkMountFile " + catalinaBase + "conf/jk/mountfile.properties." + workerName);
    //String[] excludedExts = new String[] {"*.gif", "*.xls", "*.rtf", "*.ppt", "*.doc", "*.pdf", "*.txt", "*.properties", "*.out" };
    //for(int i = 0; i < excludedExts.length; i++) {
    //  mountfile.println("!/" + excludedExts[i] + "=" + workerName);
    //}
    //mountfile.println("-/WEB-INF/*=" + workerName);
    //mountfile.println("-/META-INF/*=" + workerName);
    
    Container[] children2 = host.findChildren();
    for(int j = 0; j < children2.length; j++) {
      if(children2[j] instanceof Context) {
        Context context = (Context)children2[j];
        String ctxPath  = context.getPath();
        //TBD: is this absolute?
        String docBase = context.getServletContext().getRealPath("/").replace('\\', '/');
        if(!docBase.endsWith("/")) {
          docBase = docBase + "/";
        }
        
        httpdconf.println();
        httpdconf.println("######### " + ctxPath + " ######### ");
        httpdconf.println();
        httpdconf.println("  Alias " + ctxPath + " \"" + docBase + "\"");
        httpdconf.println();
        
        httpdconf.println("  <Directory \"" + docBase + "\">");

        String[] wf = context.findWelcomeFiles();
        if(wf != null && wf.length > 0) {
          httpdconf.print("    DirectoryIndex ");
          for( int k=0; k<wf.length ; k++ ) {
            httpdconf.print(wf[k] + " ");
          }
          httpdconf.println();
        }
        httpdconf.println("    Options " + options);
        httpdconf.println("  </Directory>");
        
        // Deny serving any files from WEB-INF, META-INF
        httpdconf.println();
        httpdconf.println("  <Directory \"" + docBase + "WEB-INF/\">");
        httpdconf.println("    Options None");
        httpdconf.println("    AllowOverride None");
        httpdconf.println("    Deny from all");
        httpdconf.println("  </Directory>");
        httpdconf.println("  <Directory \"" + docBase + "META-INF/\">");
        httpdconf.println("    Options None");
        httpdconf.println("    AllowOverride None");
        httpdconf.println("    Deny from all");
        httpdconf.println("  </Directory>");

        httpdconf.println();

        //ensure all j_security_check requests are handled by Tomcat
        httpdconf.println("  JkMount " + ctxPath + "/*/j_security_check" + " ajp13");
        httpdconf.println("  JkMount " + ctxPath + "/j_security_check" + " ajp13");
        httpdconf.println();

        //if(context.getLoginConfig() != null && context.getLoginConfig().getLoginPage() != null) {
        //  String loginPage = context.getLoginConfig().getLoginPage();
        //  int lpos = loginPage.lastIndexOf("/");
        //  String jscurl = loginPage.substring(0,lpos+1) + "j_security_check";
        //  if(!(jscurl.startsWith("/"))) {
        //    jscurl = "/" + jscurl;
        //  }
        //  httpdconf.println("  JkMount " + ctxPath + jscurl + " ajp13");
        //  //httpdconf.println("  JkMount " + ctxPath + jscurl + " " + workerName);
        //  //mountfile.println(ctxPath + jscurl + "=" + workerName);
	//}

	String[] servletMaps = context.findServletMappings();
	for(int ii=0; ii < servletMaps.length; ii++) {
          if(!(servletMaps[ii].startsWith("/"))) {
            servletMaps[ii] = "/" + servletMaps[ii];
          }
          //so that we don't map the whole thing
          if(servletMaps[ii].length() > 1) {
            httpdconf.println("  JkMount " + ctxPath + servletMaps[ii] + " ajp13");
            //httpdconf.println("  JkMount " + ctxPath + servletMaps[ii] + " " + workerName);
            //mountfile.println(ctxPath + servletMaps[ii] + "=" + workerName);
          }
	}
        
      }
    }

    //mountfile.close();
    httpdconf.close();
  }

  /*
   * Walk thru the hosts, and write the appropriate lines
   */
  private void recreateApacheConf(Engine engine) throws Exception {

    PrintWriter workfile = new PrintWriter(new FileOutputStream(catalinaBase + "conf/jk/workers.properties"));
    PrintWriter httpdconf = new PrintWriter(new FileOutputStream(catalinaBase + "conf/jk/httpd.conf.include"));
    
    
    //writeHttpdConfHead();
    httpdconf.println("<IfModule !mod_jk.c>");
    httpdconf.println("  LoadModule jk_module " + modulePath);
    httpdconf.println("</IfModule>");
    httpdconf.println("JkWorkersFile " + catalinaBase + "conf/jk/workers.properties");
    httpdconf.println("JkLogFile " + logfile);
    httpdconf.println("JkLogLevel error");
    httpdconf.println("JkLogStampFormat \"[%a %b %d %H:%M:%S %Y] \"");
    httpdconf.println("JkOptions +ForwardKeySize +ForwardURICompatUnparsed -ForwardDirectories");
    httpdconf.println("JkRequestLogFormat \"%w %V %T\"");
    
    int port = 8009;
    Connector[] connectors = engine.getService().findConnectors();
    for(int i=0; i < connectors.length; i++) {
      if(connectors[i].getProtocol().startsWith("AJP")) {
        port = connectors[i].getPort();
      }
    }
    
    List workers = new ArrayList();

    Container[] children = engine.findChildren();
    for(int i=0; i < children.length; i++) {
      if(children[i] instanceof Host) {
        Host host = (Host)children[i];
        
        //String workerName = host.getName().replace('.', '_');
        //workers.add(workerName);
        //workfile.println("worker." + workerName + ".host=" + host.getName());
        //workfile.println("worker." + workerName + ".port=" + port);
        //workfile.println();
        
        recreateHostConfFiles(host);    
      }
    }
    
    //workfile.print("worker.list=");
    //for(Iterator itr = workers.iterator(); itr.hasNext(); ) {
    //  workfile.print(itr.next());
    //  workfile.print(",");
    //}
    //workfile.println();
    
    workfile.println("worker.list=ajp13");
    workfile.println("worker.ajp13.port=" + port);
    workfile.println("worker.ajp13.type=ajp13");
    
    workfile.close();
    httpdconf.close();
  }
  
}

